#include "stdafx.h"
#include "DirectX9.h"

#include "IWindow.h"
#include "IShader.h"
#include "Texture.h"
#include "VertexBuffer.h"
#include "IndexBuffer.h"
#include "ShaderManager.h"


IDirect3DDevice9*	g_pDevice = nullptr;

CDirectX9::CDirectX9()
: m_bRendering(false)
, m_pD3D(nullptr)
, m_pVertexDeclaration(nullptr)
, m_pShaderManager(nullptr)
, m_pShader(nullptr)
{
	
}

CDirectX9::~CDirectX9()
{
	assert(!m_pShader);	//	shader should be resetted

	SAFE_RELEASE(m_pVertexDeclaration);
	SAFE_RELEASE(m_pDevice);
	SAFE_RELEASE(m_pD3D);

	delete m_pShaderManager;

	g_pDevice = nullptr;
}

bool CDirectX9::Init()
{
	//	create Direct3D instance
	m_pD3D = Direct3DCreate9(D3D_SDK_VERSION);
	if (!m_pD3D)
		return false;

	//	set device params
	IWindow* pWindow = gEnv->pSystem->GetWindow();
	D3DPRESENT_PARAMETERS params;
	params.BackBufferCount = 1;
	params.SwapEffect = D3DSWAPEFFECT_DISCARD;
	params.BackBufferFormat = D3DFMT_X8R8G8B8;
	params.BackBufferWidth = pWindow->GetClientWidth();
	params.BackBufferHeight = pWindow->GetClientHeight();
	params.AutoDepthStencilFormat = D3DFMT_D24S8;
	params.EnableAutoDepthStencil = true;
	params.Flags = 0;
	params.hDeviceWindow = static_cast<HWND>(pWindow->GetHandle());
	params.MultiSampleType = D3DMULTISAMPLE_NONE;
	params.MultiSampleQuality = 0;
	params.FullScreen_RefreshRateInHz = 0;
	params.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
	params.Windowed = true;

	//	create device
	if (FAILED(m_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, params.hDeviceWindow, D3DCREATE_HARDWARE_VERTEXPROCESSING, &params, &m_pDevice)))
		return false;

	g_pDevice = m_pDevice;

	InitVertexDeclaration();
	m_pShaderManager = new CShaderManager();

	return true;
}

IShader* CDirectX9::CreateShader(const char* name)
{
	return m_pShaderManager->CreateShader(name);
}

ITexture* CDirectX9::CreateTexture(uint width, uint height, uint levels, ETextureFormat format, bool dynamic)
{
	return new CTexture(width, height, levels, format, dynamic);
}

ITexture* CDirectX9::CreateTexture(const char* filepath, uint width, uint height, uint levels, ETextureFormat format, bool dynamic)
{
	return new CTexture(filepath, width, height, levels, format, dynamic);
}

IIndexBuffer* CDirectX9::CreateIndexBuffer(uint length, bool dynamic)
{
	return new CIndexBuffer(length, dynamic);
}

IVertexBuffer* CDirectX9::CreateVertexBuffer(uint length, bool dynamic)
{
	return new CVertexBuffer(length, dynamic);
}

void CDirectX9::StartRendering()
{
	m_pDevice->BeginScene();
	m_bRendering = true;
}

void CDirectX9::FinishRendering()
{
	m_pDevice->EndScene();
	m_bRendering = false;
}

void CDirectX9::SwapBuffers()
{
	m_pDevice->Present(nullptr, nullptr, nullptr, nullptr);
}

void CDirectX9::ClearBuffer(uint flags, const Color& color, float z)
{
	assert(flags != 0);

	uint dx_flags = 0;
	if (flags & eClearFlag_Target)
		dx_flags |= D3DCLEAR_TARGET;
	if (flags & eClearFlag_Depth)
		dx_flags |= D3DCLEAR_ZBUFFER;
	if (flags & eClearFlag_Stencil)
		dx_flags |= D3DCLEAR_STENCIL;

	m_pDevice->Clear(0, nullptr, dx_flags, color.color, z, 0);
}

void CDirectX9::SetShader(IShader* pShader)
{
	m_pShader = pShader;
}

void CDirectX9::SetTexture(ITexture* pTexture)
{
	assert(m_pShader);
	 m_pShader->SetSamplerTexture("diffuse_sampler", pTexture);
}

void CDirectX9::DrawPrimitives(EPrimitiveType ptype, const IVertexBuffer* vertices, uint16 vertex_count, const IIndexBuffer* indices, uint16 index_count)
{
	assert(IsRenderingStarted());
	assert(vertices);
	assert(indices);
	assert(m_pShader);

	const CVertexBuffer* vb = static_cast<const CVertexBuffer*>(vertices);
	const CIndexBuffer* ib = static_cast<const CIndexBuffer*>(indices);

	m_pDevice->SetVertexDeclaration(m_pVertexDeclaration);
	m_pDevice->SetStreamSource(0, *vb, 0, sizeof(S2DVertex));
	m_pDevice->SetIndices(*ib);

	//	shader
	uint passes_count = m_pShader->BeginTechnique("main");
	for (uint i = 0; i < passes_count; ++i)
	{
		m_pShader->BeginPass(i);

		//	render
		D3DPRIMITIVETYPE primitive_type = D3DPT_TRIANGLELIST;
		uint primitive_count = 0;
		switch (ptype)
		{
		case ePrimType_TriangleList:
			primitive_type = D3DPT_TRIANGLELIST;
			primitive_count = index_count / 3;
			break;

		case ePrimType_TriangleStrip:
			primitive_type = D3DPT_TRIANGLESTRIP;
			primitive_count = index_count - 2;
			break;

		default:
			Error("Primitive type is not supported");
		}
		m_pDevice->DrawIndexedPrimitive(primitive_type, 0, 0, vertex_count, 0, primitive_count);
		m_pShader->EndPass();
	}
	m_pShader->EndTechnique();

	//	reset device state
	m_pDevice->SetVertexDeclaration(nullptr);
	m_pDevice->SetStreamSource(0, nullptr, 0, 0);
	m_pDevice->SetIndices(nullptr);
}

void CDirectX9::InitVertexDeclaration()
{
	D3DVERTEXELEMENT9 decl[] = 
	{
		{0, 0,  D3DDECLTYPE_FLOAT2,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
		{0, 8,	D3DDECLTYPE_FLOAT2,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
		{0, 16, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0},
		D3DDECL_END()
	};
	m_pDevice->CreateVertexDeclaration(decl, &m_pVertexDeclaration);
}

bool CDirectX9::IsRenderingStarted() const
{
	return m_bRendering;
}